package com.yiboshi.rehrs.helper;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.thread.ThreadUtil;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.github.benmanes.caffeine.cache.Cache;
import com.yiboshi.rehrs.config.RehrsSystemConfig;
import com.yiboshi.rehrs.exception.ZyantExceptionUtils;
import com.yiboshi.rehrs.util.CacheUtils;
import com.yiboshi.rehrs.util.DateUtils;
import com.yiboshi.rehrs.util.Sha1Util;
import com.yiboshi.rehrs.util.StrUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author 472464220
 */
@Component
@Slf4j
public class RedisLocker {


    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    //@Autowired
    //@Lazy
    //protected ServerConfig serverConfig;

    String lock = "zyant-lock:";

    String lock_hash = "zyant-lock-hash:";


    public boolean delLock(final String key){
        final String  keyNew = lock+key;
        boolean success=  stringRedisTemplate.delete(keyNew);
        if(!success){
            success = CollectionUtils.isEmpty(getKeys(keyNew));
        }
        if(!success){
            log.error("delLock:{} fail",keyNew);
        }
        return success;
    }

    public boolean delLocks(final String pattern){
        Set<String> keys = getKeys(pattern);
        if(CollectionUtils.isEmpty(keys)){
            return true;
        }

        Long delete = stringRedisTemplate.delete(keys);
        log.info("delLocks:{},delete:{}",keys.stream().collect(Collectors.joining(",")),delete);
        return true;
    }

    public Set<String> getKeys(final String pattern){
        final String  keyNew = lock+pattern;
        return stringRedisTemplate.keys(keyNew + "*");
    }

    public boolean updateLockTime(final String key, final long seconds){
        final String  keyNew = lock+key;
        return stringRedisTemplate.expire(keyNew,seconds,TimeUnit.SECONDS);
    }

    public void compareAndAddLockTime(final String key, final long seconds){
        final String  keyNew = lock+key;
        Long expire = stringRedisTemplate.getExpire(keyNew);//seconds
        if(expire<seconds&&expire>0){
            log.info("延迟获取列表数据,{}，时间推迟：{} s",keyNew,seconds);
            stringRedisTemplate.expire(keyNew,expire+seconds,TimeUnit.SECONDS);
        }
    }



    //ConcurrentSkipListSet<Thread> keyValueSet = new ConcurrentSkipListSet<>();

    //RedisLockTimeOutException
    public boolean lock(final String key,int seconds, int waitSeconds) {
        waitSeconds = waitSeconds>=seconds?seconds-1:waitSeconds;
        boolean success =false;
        long id = IdWorker.getId();
        String lockValue = id+"";
        Long now = System.currentTimeMillis();
        Long  waitMillis = Long.valueOf(waitSeconds*1000);
        Long sleepTime = waitMillis/4;
        if(sleepTime>1000){
            sleepTime = 1000L;
        }
        while(System.currentTimeMillis()-now<waitMillis){
            success = tryLock(key, seconds,lockValue);
            if(success){
                return true;
            }
            ThreadUtil.sleep(sleepTime);
        }
        //ExceptionUtil.throwRedisLockException("网络繁忙请稍后再试(RTO)");
        return false;
    }
    /*public boolean delReentrantLock(final String key){
        delLock(key);
        //keyValueSet.remove(Thread.currentThread());
        return true;
    }*/

    public boolean tryLock(final String key,  final long seconds,String value) {
        final String  keyNew = lock+key;
        boolean success = setNxEx(keyNew,seconds,value);
        if(success&&seconds>3600){
            log.warn("{},锁定时间seconds:{}秒",keyNew,seconds);
        }
        return success;
    }


    public boolean setNxEx(String keyNew, long seconds, String value) {
        Boolean result =  stringRedisTemplate.execute(new RedisCallback<Boolean>() {
            @Override
            public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
                RedisSerializer valueSerializer = stringRedisTemplate.getValueSerializer();
                RedisSerializer keySerializer = stringRedisTemplate.getKeySerializer();
                Object obj = connection.execute("set", keySerializer.serialize(keyNew),
                        valueSerializer.serialize(value),
                        "NX".getBytes(StandardCharsets.UTF_8),
                        "EX".getBytes(StandardCharsets.UTF_8),
                        String.valueOf(seconds).getBytes(StandardCharsets.UTF_8));
                return obj != null;
            }
        });
        return result;
    }


    public boolean tryLock(final String key,  final long seconds) {
        Cache<String, Object> builder = null;
        /*if(seconds>10){
            builder = CacheUtils.builder(10);
            Object ifPresent = builder.getIfPresent(key);
            if(ifPresent!=null){
                return false;
            }
        }*/

        boolean success =  tryLock(key,seconds,"1");
        if(builder!=null){
            /*if(success){
                builder.put(key,1);
            }*/
        }
        return success;
    }


    public boolean tryLockByServerIp(final String key,  final long seconds) {
        String serverIpAndPort = RehrsSystemConfig.getServerIpAndPort();
        if(StrUtils.isBlank(serverIpAndPort)){
            return false;
        }
        long lockSeconds = seconds<=0?60*10:seconds;
        String serverKey = key+":"+RehrsSystemConfig.getServerIpAndPort();
        return tryLock(serverKey,lockSeconds, DateUtils.dateTimeToInt(LocalDateTime.now()).toString());
    }

    public boolean delLockByServerIp(final String key){
        String serverKey = key+":"+RehrsSystemConfig.getServerIpAndPort();
        return delLock(serverKey);
    }







    //自增缓存
    public long uniqueIncrement(String code, Function<String, Long> function) {
        String key = toUniqueIncrementKey(code);
        Long increment = existAndIncr(key);
        if(increment.intValue()>0){
            return increment;
        }
        lock(key,30,31);
        try{
            increment = existAndIncr(key);
            if(increment.intValue()>0){
                return increment;
            }
            Long apply = function.apply(code);
            setNxEx(key, 3600*4, apply + "");
            Long aLong = existAndIncr(key);
            ZyantExceptionUtils.e(aLong.intValue()==0,"设置自增编码失败(INCR)");
            return aLong;
        }finally {
            delLock(key);
        }
    }
    public boolean delUniqueIncrementKey(String code){
        String key = toUniqueIncrementKey(code);
        Set<String> keys = stringRedisTemplate.keys(key);
        if(CollUtil.isEmpty(keys)){
            return true;
        }
        log.info("del keys:{}",keys);
        return stringRedisTemplate.delete(keys)>0;
    }

    // 存在自增 不存在返回0
    public static final String SCRIPT_EXIST_INCR = "if redis.call('EXISTS', KEYS[1]) == 1 then return redis.call('INCR', KEYS[1]) else return 0 end";
    // 脚本sha1值
    private static final String SCRIPT_EXIST_INCR_SHA1 = Sha1Util.encrypt(SCRIPT_EXIST_INCR);

    public Long existAndIncr(String key) {
        return stringRedisTemplate.execute(new RedisScript<Long>() {
                                               @Override
                                               public String getSha1() {
                                                   return SCRIPT_EXIST_INCR_SHA1;
                                               }
                                               @Override
                                               public Class<Long> getResultType() {
                                                   return Long.class;
                                               }

                                               @Override
                                               public String getScriptAsString() {
                                                   return SCRIPT_EXIST_INCR;
                                               }

                                           }, Collections.singletonList(key)
        );
    }

    public static String toUniqueIncrementKey(String code) {
        return "ahm:unique:incr:"+code;
    }




}



